package mpscheduling;

import java.io.*;
import java.util.*;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author jesseday
 */
public class Scheduler {
    
    public ArrayList<Process> ProcessList = new ArrayList<Process>();
    public ArrayList<Process> ReadyQueue = new ArrayList<Process>();
    public ArrayList<LinkedList> Gantt = new ArrayList<LinkedList>();
    public ArrayList<Processor> Processors = new ArrayList<Processor>();
    public ArrayList<ArrayList<Process>> ReadyQueueList = new ArrayList<ArrayList<Process>>();
    public AtomicInteger time = new AtomicInteger(0);
    public Semaphore mutex = new Semaphore(1);
    public int p1, p2, quantum;
    public String filename;
    public boolean useFile;        
    public int algo;
    public boolean common, push = false, pull = false;
    public int pushFactor = 1, pullFactor = 0;
    
    public Scheduler (int p1, int p2, int q, String fname, boolean useFile, 
            int algo, boolean c)
    {
        this.p1=p1;
        this.p2=p2;
        this.quantum = q;
        this.filename = fname;
        this.useFile = useFile;
        this.algo = algo;
        this.common = c;
    }
    
    public ArrayList<Process> getProcessList()
    {
        return ProcessList;
    }
    

    public void run() {

        if(useFile)
        {
            try {
                inputProcessFile(filename);
            } catch (Exception ex) {
                Logger.getLogger(Scheduler.class.getName()).log(Level.SEVERE, null, ex);
            }
        }else
        {
            createProcesses(p2);
        }
        
        if(common)
            createProcessors(p1);            
        
        while(!allDone())
        {
            
            if(common)
                updateRQ();
            else
            {
                updateRQnotcommon();
                if(push)
                    push();
                if(pull)
                    pull();
            }
                
            Thread[] threads = new Thread[Processors.size()];
            
            for(int i=0; i<Processors.size(); i++)
            {
                
                threads[i] = new Thread(Processors.get(i));
                threads[i].start();
                
            }
            
            for(int i=0; i<Processors.size(); i++){
            
                try{
                    threads[i].join();
                }catch (InterruptedException ie){}
            }
            
            time.incrementAndGet();
            
            
        }
        
    }
    
    public void push()
    {
        Processor p1, p2;
        Process move;
        boolean done = false;
        for(int i=0; i<Processors.size(); i++)
        {
            p1 = Processors.get(i);
            if(p1.ReadyQueue.size()>pushFactor)
            {
                for(int j=Processors.size(); j>0; j--)
                {
                    p2 = Processors.get(j-1);
                    if(p2.ReadyQueue.size()<pushFactor)
                    {
                        move = p1.ReadyQueue.remove(p1.ReadyQueue.size()-1);
                        p2.ReadyQueue.add(move);
                        j = 0;
                        done = true;
                    }
                }
                if(!done)
                {
                    int moveTo = (p1.pnum + 1)%(Processors.size()-1);
                    move = p1.ReadyQueue.remove(p1.ReadyQueue.size()-1);
                    Processors.get(moveTo).ReadyQueue.add(move);
                }
                done = false;
            }
            
        }
    }
    
    public void pull()
    {
        Processor idleProcessor, victimProcessor;
        Process move;
        //For all processors
        for(int i=0; i<Processors.size(); i++)
        {
            idleProcessor = Processors.get(i);
            
            //If processor has empty RQ and is idle
            if(idleProcessor.ReadyQueue.isEmpty()
                    &&idleProcessor.currentProcess==null)
            {
                //For all processors 
                for(int j=0; j<Processors.size(); j++)
                {
                    victimProcessor = Processors.get(j);
                    
                    //If processor has more than pullFactor (0) processes in its RQ 
                    //and is currently running a process, move the last process to the 
                    //idle processor
                    if(victimProcessor.ReadyQueue.size()>pullFactor
                            &&victimProcessor.currentProcess!=null)
                    {
                        move = victimProcessor.ReadyQueue.remove(victimProcessor.ReadyQueue.size()-1);
                        idleProcessor.ReadyQueue.add(move);
                        j = Processors.size();
                    }
                }
            }
        }
        
    }
    
    public void outWithIt()
    {
        for(int i=0; i<Gantt.size(); i++)
        {
            LinkedList l = Gantt.get(i);
            
            for(int j=0; j<l.size(); j++)
            {
                System.out.print(l.get(j)+" ");
            }
            System.out.println();
        }
    }
    
    public void updateRQ()
    {
        for(int i = 0; i < ProcessList.size(); i++)
        {
            if(ProcessList.get(i).arrivalTime() == time.get())
                ReadyQueue.add(ProcessList.get(i));
        }   
    }
    
    public void updateRQnotcommon()
    {
        for(int i = 0; i < ProcessList.size(); i++)
        {
            if(ProcessList.get(i).arrivalTime() == time.get())
                Processors.get(0).ReadyQueue.add(ProcessList.get(i));
        }   
    }
    
    public boolean allDone()
    {
        for(int i = 0; i < ProcessList.size(); i++)
        {
            if(!ProcessList.get(i).isDone())
                return false;
        }
        return true;
    }
    
    
    
    
    public void printStats()
    {
        System.out.println();
        System.out.printf("%14s", "Process Number");
        System.out.printf("%18s", "Turnaround Time");
        System.out.printf("%12s", "Wait Time");
        System.out.printf("%15s", "Arrival Time");
        System.out.printf("%13s\n", "Burst Time");
        
        for(int i = 0; i < 74; i++)
            System.out.print("-");
        System.out.println();
        
        double avgTT = 0, avgWT = 0;
        
        for(int i = 0; i < ProcessList.size(); i++)
        {
            System.out.printf("%7d", ProcessList.get(i).processNum());
            System.out.printf("%20d", ProcessList.get(i).turnaroundTime());
            System.out.printf("%14d", ProcessList.get(i).waitTime());
            System.out.printf("%14d", ProcessList.get(i).arrivalTime());
            System.out.printf("%14d\n\n", ProcessList.get(i).initialBurst());
            avgTT += ProcessList.get(i).turnaroundTime();
            avgWT += ProcessList.get(i).waitTime();
        }
        
        avgTT /= ProcessList.size();
        avgWT /= ProcessList.size();
        System.out.printf("%10s", "Average");
        System.out.printf("%18.4f", avgTT);
        System.out.printf("%14.4f\n", avgWT);
    }
    
    public void createProcessors(int num)
    {
        for(int i = 0; i < num; i++)
        {
            LinkedList<Integer> g = new LinkedList<Integer>();
            Gantt.add(g);
            Processors.add(i, new Processor(i+1,time,ReadyQueue,g,mutex,algo,quantum));
            
        }
        
    }
    
    
    public void createProcessor(int num)
    {
        LinkedList<Integer> g = new LinkedList<Integer>();
        Gantt.add(g);
        ArrayList<Process> rq = new ArrayList<Process>();
        ReadyQueueList.add(rq);
        Processors.add(num, new Processor(num+1,time,rq,g,mutex,algo,quantum));
    }
    
    public void createProcessor(int num, int a, int q)
    {
        LinkedList<Integer> g = new LinkedList<Integer>();
        Gantt.add(g);
        ArrayList<Process> rq = new ArrayList<Process>();
        ReadyQueueList.add(rq);
        Processors.add(num, new Processor(num+1,time,rq,g,mutex,a,q));
    }
    
    public void createProcesses(int num)
    {   
        for(int i = 0; i < num; i++)
        {
            int arrival = (int)(Math.random() * 20);
            int burst = (int)(Math.random() * 20) + 1;
            ProcessList.add(i, new Process(i+1, arrival, burst, time));
        }
        
    }
    
    public void inputProcessFile(String filename) throws Exception
    {
        BufferedReader reader = new BufferedReader(new FileReader(new File(filename)));
        
        String line;
        int linenum = 0;
        
        while((line = reader.readLine()) != null)
        {
            String[] process = line.split(" ");
            
            int pnum = Integer.parseInt(process[0]);
            int arrival = Integer.parseInt(process[1]);
            int burst = Integer.parseInt(process[2]);

            ProcessList.add(linenum, new Process(pnum, arrival, burst, time));
            
            linenum++;
        }
    }
}
